Sexy Swing App – The Rating Renderer

May 24, 2008

The iTunes style rating render has three unique states:

Unselected
Selected, focused
Selected, unfocused

To render these three states, we’ll need five individual icons, which includes the subtle dot icons used to indicate the clickable area on the rating component (I didn’t include support for changing the rating in the example below). Here are the five icons I created to use when composing the rating renderer:

Unselected
Selected, unfocused
Selected, focused (the icon is white, so you can’t really see it here)
Selected, unfocused
Selected, focused (the icon is white, so you can’t really see it here)

The foundation of this renderer is the data that backs it. Here’s the code for the Rating object:

public enum Rating {
    NO_RATING, ONE_STAR, TWO_STARS, THREE_STARS, FOUR_STARS, FIVE_STARS;
    public static Rating getRating(int ratingInteger) {
        if (ratingInteger < 0 || 100 < ratingInteger) {
            throw new IllegalArgumentException("Rating must be between " +
                    "1 and 100");
        }
        return Rating.values()[Math.round(ratingInteger / 20)];
    }
}

Note that this rating component was intended to translate from the iTunes 1-100 based rating, hence the divide by 20.

Here’s the actual RatingComponent which assembles a series of icons that represent a supplied Rating. To make the component render as selected, focused, or both, the focus and selected flags can be set.

public class RatingComponent {

    private JPanel fComponent = new JPanel();
    private Rating fRating;
    private boolean fSelected;
    private boolean fFocused = true;
    private static ImageIcon FOCUSED_SELECTED_STAR =
            new ImageIcon(ITunesRatingTableCellRenderer.class.getResource(
                    "/com/orr/widgets/images/itunes_star_focused_selected.png"));
    private static ImageIcon UNFOCUSED_SELECTED_STAR =
            new ImageIcon(ITunesRatingTableCellRenderer.class.getResource(
                    "/com/orr/widgets/images/itunes_star_unfocused_selected.png"));
    private static ImageIcon UNSELECTED_STAR =
            new ImageIcon(ITunesRatingTableCellRenderer.class.getResource(
                    "/com/orr/widgets/images/itunes_star_unselected.png"));
    private static ImageIcon UNFOCUSED_DOT =
            new ImageIcon(ITunesRatingTableCellRenderer.class.getResource(
                    "/com/orr/widgets/images/itunes_dot_unfocused.png"));
    private static ImageIcon FOCUSED_DOT =
            new ImageIcon(ITunesRatingTableCellRenderer.class.getResource(
                    "/com/orr/widgets/images/itunes_dot_focused.png"));

    RatingComponent(Rating rating) {
        fRating = rating;
        buildRatingPanel();
    }

    private void buildRatingPanel() {
        // definte the FormLayout columns and rows.
        FormLayout layout = new FormLayout("","fill:p:grow");
        // create the cell constraints to use in the layout.
        CellConstraints cc = new CellConstraints();
        // create the builder with our panel as the component to be filled.
        PanelBuilder builder = new PanelBuilder(layout, fComponent);

        fComponent.setOpaque(true);
        for (int i=0; i<Rating.values().length-1; i++) {
            RatingLabel label = new RatingLabel(i);
            builder.appendColumn("p");
            builder.add(label, cc.xy(builder.getColumn(), 1));
            builder.nextColumn();
        }
    }

    public JComponent getComponent() {
        return fComponent;
    }

    public void setSelected(boolean selected) {
        fSelected = selected;
    }

    public void setFocused(boolean focused) {
        fFocused = focused;
    }

    ///////////////////////////////////////////////////////////////////////////
    // Custom class to return icon for rating indicator based on its position
    // in the larger component and the current rating.
    ///////////////////////////////////////////////////////////////////////////

    private class RatingLabel extends JLabel {

        private int fPosition;

        private RatingLabel(int position) {
            fPosition = position;
        }

        @Override
        public Icon getIcon() {
            Icon retVal = null;
            if (fRating == null || fRating == Rating.NO_RATING) {
                retVal = null;
            } else if (fPosition < fRating.ordinal()) {
                retVal = getStarIcon();
            } else if (fSelected) {
                retVal = getDotIcon();
            }
          return retVal;
        }

        private Icon getStarIcon() {
            Icon retVal;
            if (!fSelected) {
                retVal = UNSELECTED_STAR;
            } else if (fFocused) {
                retVal = FOCUSED_SELECTED_STAR;
            } else {
                retVal = UNFOCUSED_SELECTED_STAR;
            }
            return retVal;
        }

        private Icon getDotIcon() {
            Icon retVal = null;
            if (fFocused) {
                retVal = FOCUSED_DOT;
            } else {
                retVal = UNFOCUSED_DOT;
            }
            return retVal;
        }
    }

One final note: I found rendering performance to be too slow if I tried to construct these components on the fly during the painting process. Caching a RatingComponent for each value in the Rating enumeration greatly improved performance.

Advertisements

One Response to “Sexy Swing App – The Rating Renderer”

  1. alex Says:

    great article!!


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

%d bloggers like this: