Skip to main content

Extreme List View

Posted by zixle on December 18, 2006 at 8:39 AM PST

In my last blog we finally released the source for
this years Extreme GUI Makeover talk; hooray! There are a number of
aspects of the app that are worth exploring. For this blog, I want to
explore how the extreme list view was done. What I'm calling the
extreme list view can be seen in the following screen shot:

As you may be able to surmise from the screen shot, and the name, the
extreme list view is nothing more than a JList with a custom
ListCellRenderer. The ListCellRenderer is implemented as a JPanel,
that uses GroupLayout to achieve the desired effect.

If you look at the layout of each cell, you'll notice the layout is
nearly a grid, but not quite. The first row has two columns, the
second row one column, and the third row two columns. The reason this
doesn't fit well with a grid is that the second columns of the first
and last row shouldn't have the same size. Most grid based layout
managers enforce all columns to have the same size. I've no doubt
there is some grid based layout managers that does this, but not
GridBagLayout. Of course you could always implement this as nested
panels, but that should be avoided when possible!

GroupLayout does not enforce the components be in a grid, although
grid based layouts are certainly possible with GroupLayout. As such,
GroupLayout is particularly well suited for this layout, and it gave
me another chance to play with GroupLayout. Here's how this layout is
implemented in terms of GroupLayout. For those wishing to learn more
about GroupLayout before getting into the nitty gritty, we've just
updated the Swing Tutorial to include coverage of GroupLayout, which
can be found here.

GroupLayout treats each axis independently. Looking at the horizontal
axis, visually you can see space needs to be provided for the image,
followed by three rows of text. This translates to:

(IP + rows)

Which equates to a sequential group with the image component, followed
by the rows. As the rows are positioned in the same
space, horizontally, the components in each row need to be placed in a
parallel group:

(I + [R1 R2 R2])

I'm using parens to denote sequential groups, and brackets to denote
parallel groups. The first and third row consist of two components
each for the differing labels. As the labels in each are placed one
after another, a sequential group is used:

(I + [(S + C0) C1 (C2 + F)])

The key for this layout is that all extra space should go to the
subject and content text. This is achieved by explicitly specifing the
subject and context text have a minimum and preferred size of 1, and a
maximum size of Integer.MAX_VALUE:

  addComponent(component, 1, 1, Integer.MAX_VALUE);

All other components are forced to use their preferred size for the
min/pref/max. As JLabels already do this, you can use the single
argument addComponent, or explicitly request this behavior with the
following code:

  addComponent(component, PREFERRED_SIZE, PREFERRED_SIZE, PREFERRED_SIZE);

Viewed graphically, this looks like:

Here's the complete code for creating the horizontal grouping:

  GroupLayout.SequentialGroup hg = layout.createSequentialGroup();
  layout.setHorizontalGroup(hg);
  // Add the image panel with a fixed size
  hg.addComponent(imagePanel, IS, IS, IS).
    // Create parallel group that will contain the rows
    addGroup(layout.createParallelGroup().
      // First row contains the subject, and date labels
      // Notice the subject is infinitely resizable
      addGroup(layout.createSequentialGroup().
        addComponent(subjectLabel, 1, 1, Integer.MAX_VALUE).
        addComponent(dateLabel)).
      // Second row is a single label that is infinitely resizable.
      addComponent(labels[0], 1, 1, Integer.MAX_VALUE).
      // Third row contains two labels: text and from. The label
      // is infinitely resizable, where as the from label is fixed
      // at its preferred size.
      addGroup(layout.createSequentialGroup().
        addComponent(labels[1], 1, 1, Integer.MAX_VALUE).
        addComponent(fromLabel)));

The only other trick to mention when using a JList like this is be
absolutely sure to set the prototype cell value. If you don't, JList is
going to query the renderer for the preferred size for each and every
value in the list. As you can imagine, a layout like this is NOT
cheap! Additionally each cell should have the same size, so specifying
the protoype value is the way to go!

I'm not going to go through the code for the vertical axis, hopefully
it's not that hard to grok given the walk through of the horizontal
axis.

Here's the complete source for the app.

Enjoy!

    -Scott