ONJava.com    
 Published on ONJava.com (http://www.onjava.com/)
 See this if you're having trouble printing code examples


Java Swing, 2nd Edition

RelativeLayout, a Constraint-Based Layout Manager

by James Elliott, coauthor of Java Swing, 2nd Edition
09/18/2002

Introduction

If you pick up the second edition of Java Swing (literally, you know, heft the sucker), you might find it hard to believe we left anything out. In fact, for this revision, we tried to cut back significantly on anything that bordered on esoterica, to leave more space for useful explanations and practical examples.

Even so, there was enough new material to cover that we couldn't fit in everything we wanted. This was especially true for topics peripheral to the main thrust of the book, no matter how interesting we found them. Our editor came up with a nice way out, of which this article is the first installment: we're publishing some of the self-contained supplementary examples as articles on ONJava.com.

Layout managers fall firmly into the periphery when talking about Swing. They're something you use all the time with Swing containers, but they predate Swing and you can use them just as well with an AWT application. Still, layout managers play such a fundamental role in arranging the pieces of your user interface that you need to develop a good understanding of at least a couple of them so that you can work effectively. And they hold the promise of enabling your application to look polished as it moves from platform to platform, gracefully adapting to new font metrics and component shapes.

Source Code

Download the source code for this article. This zip file contains the following:
Example1.java, Example2.java, example2.xml, Example3.java, example3.xml and the RelativeLayout.jar library.

Alas, the experience of most people is just the opposite. They find the available layout managers either too simple-minded to achieve the results they have in mind, or too confusing to really understand, so they try to find example code that's "close," tinker with some parameters in a form of voodoo, and pray that what finally seems to look OK on their system will be good enough in the wild. (Yes, I know there are people who have long mastered the Zen of GridBagLayout and can refactor an interface sketch into a minimal set of nested BorderLayouts in the blink of an eye, but the rest of us could still use a little help and some more alternatives.)

To that end, Marc Loy has written an article about SpringLayout, a new layout manager in the 1.4 SDK that will dovetail nicely with visual GUI design environments. This article presents my own layout manager, called RelativeLayout.

RelativeLayout is aimed squarely at mere mortals who are trying to translate their interface ideas into portable and resizable Java implementations. I put it together both so that I could use it myself, and to act as an example of how to write a complete layout manager. You're free to use it in either capacity: download the compiled jar and read enough of the article to find out how to use RelativeLayout in your own projects, or delve into the source code and see how it works and why.

Java Swing

Related Reading

Java Swing
By Marc Loy, Robert Eckstein, Dave Wood, James Elliott, Brian Cole

Using RelativeLayout

The basic idea behind RelativeLayout is to let you specify the relationships between elements in your interface, and let the layout manager sort out the details. To demonstrate how it works, let's put together an "About Box" for an imaginary application. These things often start as napkin sketches (or design documents from the creative department that might as well be) with notes about how pieces of the interface relate to each other. Something like this:


Figure 1. Typical UI sketch.

These sorts of relationships are exactly what RelativeLayout is designed to support. We'll look at two different ways of setting them up. The first approach is to directly create and add the constraints through Java code, as illustrated by Example1.java, which we'll examine first. If you want to see how it behaves before reading about how it works, download RelativeLayout.jar to the same directory, and compile and run the example as follows:

javac -classpath RelativeLayout.jar Example1.java
java -classpath RelativeLayout.jar:. Example1

Try resizing the window a couple of times to see how the constraints interact with each other. Before delving into the source code of the example, it's worth introducing the basic concepts underlying RelativeLayout.

Attributes and Constraints

RelativeLayout positions components using eight attribute types that it can examine and manipulate. The figure below shows all of the attributes as they apply to a large label. There are four attributes that apply along the horizontal axis, and which are shown in green: Left, Horizontal Center, Right, and Width. The rest of the attributes, shown in blue, apply to the vertical axis: Top, Vertical Center, Bottom, and Height.


Figure 2. Attribute types.

When a component is laid out, RelativeLayout establishes values for all eight attributes using information about how you want things arranged. The attributes are redundant -- of the four attributes that apply to a given axis, if you know any two, you can calculate the other two. So when you're telling RelativeLayout how to position a component, you need to assign values to any two of the attributes on each axis. And since components have built-in ideas about their own preferred widths and heights, you can get away with supplying even less information.

To lay out a component with RelativeLayout, you generally need to specify only one attribute for each axis, and leave the width and height at their natural values. If you do specify width or height, you still need to constrain one other attribute on that axis, or the component will be under-constrained. If you try to specify more than two components on the same axis, it will be over-constrained. Either problem will cause layout to fail. This is a design choice; some layout managers try to make their best guess about what to do in similar situations, but that makes them harder to predict and understand.

So how do you pin down the attributes of a component, in order to lay it out? Given the discussion above, it may not be too surprising that you add constraints to the component. Each constraint applies to a single attribute of the component, and determines its value. What makes the layout relative is that the constraints themselves get their values from other components, or the container in which layout is being performed. You'll most often use an AttributeConstraint, which lets you base one attribute on another. You tell it to start with the value of any attribute of an "anchor" component, add an integer offset (which may be zero), and assign the result to the attribute you want to constrain.

The constraints described in Figure 1 can be represented quite naturally this way. For example:

And so on. The process of setting up RelativeLayout is simply a matter of expressing these constraints in Java by creating objects that represent them. You don't need to worry about the order in which you add constraints. When it's time to actually perform the layout, RelativeLayout will analyze all of the constraints it's been given and make sure that there are enough (but not too many), that they're consistent, and that there are no circular dependencies. Let's examine the relevant code in the example program to make this concrete.

Example 1: the Straight Java Approach.

The showAboutBox() method is where all the action is. It creates the JFrame that contains everything, builds components to put into it, and sets up the constraints. The first couple of lines create the frame and a RelativeLayout, which will be used as its layout manager. (Once again, you can download the source for the entire example if you'd like to follow along in context, glance at the import statements, and the like.) You can also refer to the JavaDoc for the relativelayout package if you want to research the details of any of the classes it introduces.

public static JFrame showAboutBox() {
   // Create the about box and assign it a RelativeLayout.
   final JFrame aboutBox = new JFrame("About Example 1");
   aboutBox.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
   RelativeLayout ourLayout = new RelativeLayout();
   aboutBox.getContentPane().setLayout(ourLayout);

So far we're on familiar ground, and there's not much surprising in the next chunk, either.

// Add the application title, with a font size of twenty points.
   JLabel title = new JLabel("Example 1");
   title.setFont(title.getFont().deriveFont(20.0f));
   aboutBox.getContentPane().add(title, "title");

One detail worth pointing out in that last line is that when we add the component to aboutBox, the second parameter assigns it a logical name (in this case, "title") within RelativeLayout. This is the name we'll use to set constraints for the component, or to use it as an anchor for another component's constraints. Each component being laid out needs its own unique name.

Next we get to the core of the example, setting up constraints.

// Position the title as specified by the UI sketch: Ten pixels
// below the top of the window, centered horizontally.
ourLayout.addConstraint("title", AttributeType.TOP,
   new AttributeConstraint(DependencyManager.ROOT_NAME,
   AttributeType.TOP, 10));

This statement positions the top of the label ten pixels below the top of the window. It creates an AttributeConstraint for the TOP attribute of the component named "title" (the one we just created). The AttributeType class provides a type-safe enumeration of all of the different kinds of attributes with which you can work. All valid attribute types are available as public static final constants, and you use them just as shown in the code above. If you're using an editor with code completion, this makes it easier to type them, and you never have to worry about a subtle misspelling biting you at run time, or passing in an unrelated int value, because the compiler won't let you make that kind of mistake. The class also provides a static factory that can give you the instance with a particular name, which is very useful when implementing things like the XML parser used in the next example.

Now that we've determined the component and attribute we're interested in constraining, let's look at how we build the actual constraint. The constructor for AttributeConstraint takes the name of the target component on which you want to base your new constraint, the AttributeType you're interested in, and an int offset to be added to that attribute.

The way we find attributes of the container we're laying out (for example, the top of the about box) deserves a little explanation. Although you don't need to interact directly with any instances of the DependencyManager class (it's used internally by RelativeLayout to keep track of all of the interlocking dependencies, as described in the second part of this article), it does provide a useful public static final constant, ROOT_NAME. This is the logical name that RelativeLayout assigns to the container itself, so we can use it to constrain the label's top to the top of aboutBox.

That's all we need to worry about for vertical constraints, because we're happy to leave the label using its own preferred height, which provides an implicit second constraint. For the horizontal axis, we use a similar approach to center the label in the window, by linking their horizontal center attributes. In this case, we don't need to offset the anchor attribute, so we can take advantage of a simplified constructor that omits the offset parameter. This is equivalent to calling the three-argument version with a final argument of zero.

ourLayout.addConstraint("title", AttributeType.HORIZONTAL_CENTER,
   new AttributeConstraint(DependencyManager.ROOT_NAME,
   AttributeType.HORIZONTAL_CENTER));

With that, the application name's layout is established. The next chunk of lines positions the version number, and is similar but adds an interesting twist.

// Add the version number, positioned eight pixels below the title,
// and five pixels from the left edge of the window.
aboutBox.getContentPane().add(new JLabel("Version 2.0"), "version");
ourLayout.addConstraint("version", AttributeType.TOP,
   new AttributeConstraint("title", AttributeType.BOTTOM, 8));
ourLayout.addConstraint("version", AttributeType.LEFT,
   new AttributeConstraint(DependencyManager.ROOT_NAME, AttributeType.LEFT, 5));

We add a new component to the layout using the logical name "version" and proceed to constrain both its axes. This time, the top is constrained with respect to another actual component in the layout, rather than the window itself: we set the version label's TOP to be eight pixels below the BOTTOM of the title, wherever that ends up. The LEFT edge is aligned five pixels from the LEFT of the container window, as specified in the sketch. As before, one constraint per axis is enough, because the label knows its own height and width.

The constraints for the release date are very similar:

// Add the date, at the same height as the version, five pixels from
// the right edge of the window.
aboutBox.getContentPane().add(new JLabel("December, 2002"), "date");
ourLayout.addConstraint("date", AttributeType.TOP,
   new AttributeConstraint("version", AttributeType.TOP));
ourLayout.addConstraint("date", AttributeType.RIGHT,
   new AttributeConstraint(DependencyManager.ROOT_NAME, AttributeType.RIGHT, -5));

This time we aligned the TOP of our new label to the TOP of the version label. We could just has well have offset it eight pixels from the BOTTOM of the application title, as we did with the version label, but this illustrates that there are often many ways to set up your constraints that work equally well. The one thing you have to be careful of is that you don't end up with a circular chain of dependencies. You can have as many steps as you like, as long as the final one is "rooted" to the container.

We've aligned the RIGHT edge of our label five pixels in from the RIGHT edge of aboutBox. Since the axes' origin is the top left corner, negative numbers move left or up. We use an offset of -5 to move five pixels left.

The next interface element we create is the scrolling text area with information about the application. We stuff it with enough (bogus) text to cause scroll bars to appear:

// Create the scrolling details area, and fill it with enough
// "information" to scroll.
JTextArea details = new JTextArea("This is where the details go...\n");
details.setEditable(false);
details.setLineWrap(true);
details.setWrapStyleWord(true);
for (int i = 1; i < 10; i++) {
   details.append("Filler line " + i + '\n');
}
aboutBox.getContentPane().add(new JScrollPane(details), "details");

This time we have more constraints to add, because we don't want to use the default size of the text area. We want it to take up essentially all of the free space in the window.

//Position it as in the sketch, with left and right edges flush with
// the two text areas above, and top and bottom boundaries relative to
// them and the "OK" button. Notice that we can set up the constraint
// to the button even though that's not yet been added.
ourLayout.addConstraint("details", AttributeType.LEFT,
   new AttributeConstraint("version", AttributeType.LEFT));
ourLayout.addConstraint("details", AttributeType.RIGHT,
   new AttributeConstraint("date", AttributeType.RIGHT));
ourLayout.addConstraint("details", AttributeType.TOP,
   new AttributeConstraint("version", AttributeType.BOTTOM, 4));
ourLayout.addConstraint("details", AttributeType.BOTTOM,
   new AttributeConstraint("ok", AttributeType.TOP, -4));

We've provided two constraints for each axis: LEFT and RIGHT horizontally, TOP and BOTTOM vertically. The BOTTOM constraint is defined in terms of an "OK" component that doesn't yet exist. That won't cause a problem; you can add constraints in any order, and RelativeLayout will sort them out when it needs them. We're not off the hook forever, though. If we tried to make the window visible (and thereby caused the container to try to lay itself out) before providing a component with the logical name "OK," we'd see an exception at that point.

So let's go ahead and add that final piece, the "OK" button. This time, we'll introduce a new kind of constraint, the AxisConstraint, and use it to center the button horizontally. We've already seen how we could use AttributeConstraint to do that, but AxisConstraint gives you some different capabilities. It lets you define a constraint in terms of a fractional position along one of the axes of another component.


Figure 3. Horizontal and vertical axes.

As with AttributeConstraint, we have access to the container (root) component as well, so we can center the button in aboutBox by tying its HORIZONTAL_CENTER attribute to an AxisConstraint that falls at position 0.5 along the container's horizontal axis:

// Finally, add the "OK" button, demonstrating a different way to
// center a component in the window
JButton okButton = new JButton("OK");
aboutBox.getContentPane().add(okButton, "ok");
ourLayout.addConstraint("ok", AttributeType.HORIZONTAL_CENTER,
   new AxisConstraint(DependencyManager.ROOT_NAME, AttributeAxis.HORIZONTAL, 0.5));
ourLayout.addConstraint("ok", AttributeType.BOTTOM,
   new AttributeConstraint(DependencyManager.ROOT_NAME, AttributeType.BOTTOM, -10));

Notice that, when using AxisConstraint, we have another type-safe enumeration, AttributeAxis, that lets us specify which of the two axes we want to work with. Also bear in mind that, even though we've not done so here, AxisConstraint lets us express things like "this element should be 1/3 of the way down the window." We're not restricted to centering things.

For the vertical constraint, we assign an AttributeConstraint to the button's BOTTOM that subtracts ten pixels from the container's BOTTOM, achieving the goal of having it sit ten pixels above the bottom edge of the window.

That's all there is to the layout code in the example! If you haven't yet done so, you should try compiling and running it, and watching how the constraints apply as you resize the window. The running application looks a lot like the sketch we started with:


Example 1.

Example 2: Using XML to Express Constraints

XML has rapidly established itself as a convenient and flexible tool for configuring applications, and it's well supported in Java. It's not surprising that the first thing that came to mind when I was looking for a more compact and direct way to set up constraints in RelativeLayout was to use an XML file.

Whenever I need to work with XML, I start with JDOM, another great contribution from Jason Hunter and Brett McLaughlin. It provides a truly Java-centric (and massively convenient) interface for XML processing. You'll need to download the JDOM library and have it on your class path to use RelativeLayout's XML features.

If your Java SDK doesn't already include one, you'll also need an XML parser (there's one built in to Java 2 Standard Edition, versions 1.4 and later). I personally like the Apache XML Project's Xerces.

Moving the constraints to an XML file greatly simplifies our showAboutBox method. All that's left is to create the actual components, and then call XmlConstraintBuilder to add the constraints. The full source code for the revised example is in Example2.java. It starts out the same as before:

public static JFrame showAboutBox() {
   // Create the about box and assign it a RelativeLayout.
   final JFrame aboutBox = new JFrame("About Example 2");
   aboutBox.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
   RelativeLayout ourLayout = new RelativeLayout();
   aboutBox.getContentPane().setLayout(ourLayout);

Next comes the creation of the components to populate the frame. This code is no longer interspersed with constraint specifications.

// Add the application title, with a font size of twenty points.
JLabel title = new JLabel("Example 2");
title.setFont(title.getFont().deriveFont(20.0f));
aboutBox.getContentPane().add(title, "title");

// Add the version number.
aboutBox.getContentPane().add(new JLabel("Version 2.0"), "version");

// Add the date.
aboutBox.getContentPane().add(new JLabel("December, 2002"), "date");

// Create the scrolling details area, and fill it 
// with enough "information" to scroll.
JTextArea details = new JTextArea("This is where the details go...\n");
details.setEditable(false);
details.setLineWrap(true);
details.setWrapStyleWord(true);
for (int i = 1; i < 10; i++) {
   details.append("Filler line " + i + '\n');
}
aboutBox.getContentPane().add(new JScrollPane(details), "details");

// Finally, add the "OK" button.
JButton okButton = new JButton("OK");
aboutBox.getContentPane().add(okButton, "ok");

And it just takes a few lines to read in and apply the constraints. (A real program would respond more conscientiously to the potential parsing exception, though.)

// Set up the constraints for all components.
XmlConstraintBuilder builder = new XmlConstraintBuilder();
try {
   builder.addConstraints(new File("example2.xml"), ourLayout);
}
catch (Exception e) {
   e.printStackTrace();
}

Of course, all of the action in establishing constraints has been moved to the new configuration file, example2.xml, so let's look at that. It starts out with an ordinary XML preamble that identifies it as the proper type to be parsed by XmlConstraintBuilder:

<?xml version="1.0"?>
<!DOCTYPE  constraint-set
   PUBLIC "-//Brunch Boy Design//RelativeLayout Constraint Set DTD 1.0//EN"
   "http://dtd.brunchboy.com/RelativeLayout/constraint-set.dtd">

The top-level tag in the file identifies it as a constraint set, and it contains an entry for each component that needs to be constrained. The link with actual components to be constrained is established by the name attribute of the constrain tag. (There's an unfortunate clash of terms here: I'm talking about the XML attribute "name" of the tag "constrain," not one of the spatial attributes used to lay out a component. Hopefully, this won't cause too much confusion.) The value of name matches up with the name used when the component was added to the layout. The first component we constrain is the "title" label:

<constraint-set>
  <constrain name="title">
    <top>
       <toAttribute reference="_container" attribute="top" offset="10"/>
    </top>
    <horizontalCenter>
      <toAttribute reference="_container" attribute="horizontalCenter"/>
    </horizontalCenter>
  </constrain>

The structure of the XML is intended to read almost like English (well, to someone well-versed in the format and punctuation of XML, that is). Within the constrain tag there are a number of attribute tags to constrain particular attributes of the component; in this case, the top and horizontalCenter attributes. Case matters in XML, so it's important to use the "camel case" supported by XmlConstraintBuilder when creating these tags. If you've got a good code-completing editor, it may well be able to do this for you automatically by loading and understanding the XML DTD that defines a valid constraint-set document. The legal attribute tags are the same as the strings you can use with the getInstance() static factory method of the AttributeType class: left, top, right, bottom, width, height, horizontalCenter, and verticalCenter.

Within the top tag above, we specify the constraint for the top attribute of the component named title. The constraint consists of a toAttribute tag, which tells XmlConstraintBuilder to create an AttributeConstraint. The (XML) attributes of the toAttribute tag determine the parameters used to create that constraint:

<toAttribute reference="_container" attribute="top" offset="10"/>

Putting this all together, we're constraining the top of the application title to be ten pixels below the top of aboutBox. The second constraint in this section can be read in the same way. It creates an AttributeConstraint that causes the horizontal center of the title to be at the horizontal center of the window.

The next stanza in the file creates the constraints for the version number, the top of which is eight pixels below the bottom of the title, and the left edge of which is five pixels from the left edge of aboutBox:

  <constrain name="version">
    <top>
      <toAttribute reference="title" attribute="bottom" offset="8"/>
    </top>
    <left>
      <toAttribute reference="_container" attribute="left" offset="5"/>
    </left>
  </constrain>

The constraints for the release date are quite similar, though this time we just say "use the same value for top as you use for the version number's top":

  <constrain name="date">
    <top>
      <toAttribute reference="version" attribute="top"/>
    </top>
    <right>
      <toAttribute reference="_container" attribute="right" offset="-5"/>
    </right>
  </constrain>

As in the all-Java example, there are more constraints for the "details" text area, because we want to take control of its width and height. We constrain the left, right, top, and bottom, using the same techniques as we have been above:

  <constrain name="details">
    <left>
      <toAttribute reference="version" attribute="left"/>
    </left>
    <right>
      <toAttribute reference="date" attribute="right"/>
    </right>
    <top>
      <toAttribute reference="version" attribute="bottom" offset="4"/>
    </top>
    <bottom>
      <toAttribute reference="ok" attribute="top" offset="-4"/>
    </bottom>
  </constrain>

Finally, the constraints for the "OK" button, which are our last, so we also end the constraint-set itself:

  <constrain name="ok">
    <horizontalCenter>
      <toAxis reference="_container" axis="horizontal" fraction="0.5"/>
    </horizontalCenter>
    <bottom>
      <toAttribute reference="_container" attribute="bottom" offset="-10"/>
    </bottom>
  </constrain>
</constraint-set>

Notice that in the horizontalCenter constraint we used a new toAxis tag. This is how you create an AxisConstraint using the XML format. It has three attributes:

By constraining the button's horizontalCenter to 0.5 on the container's axis, we've simply centered it.

To compile and run this example, put Example2.java and example2.xml in the same directory, and make sure your class path contains JDOM and your XML parser. For example, if you've downloaded Xerces, you could use commands like the following :

javac -classpath jdom.jar:xerces.jar:RelativeLayout.jar Example2.java
java -classpath jdom.jar:xerces.jar:RelativeLayout.jar:. Example2

The window you get is essentially identical to the one from Example 1 (which was, after all, the goal). For the sake of variety, here's what it looks like in a different look and feel:


Example 2.

Example 3: Using Multiple Anchors with AttributeConstraint

This last example won't be discussed in nearly as much detail as the others, but it illustrates a useful feature that might otherwise be missed. AttributeConstraint allows you to specify the names of more than one component to use as an "anchor" for the constraint, as a comma-delimited list. When you do this, it calculates a bounding box, the smallest rectangle that contains all of the components in the list, and gives you access to the attributes of this bounding box.

To show a situation in which this can be useful, imagine you're creating a styled text editor, and you want to create a formatting dialog with a bunch of check boxes representing the different styles that can be combined, and a text area showing a sample of what the resulting text would look like. With more than a few styles, you might need multiple columns of text boxes. How can you get them to line up nicely? You could probably guess which of the checkboxes is longest, and use it as the anchor. But even if you guess right, the layout will break if someone later changes the text (for example, to localize the program for a different language).

AttributeConstraint's support for multiple anchors lets you cope with this easily. By using the bounding boxes around an entire column of choices to build the constraint for the next column, you don't have to worry about which item is the longest. This also lets you constrain the position of the sample text. Here's what the resulting window looks like:


Example 3.

Apart from creating a different set of Swing components, the Java source for this example is very similar to Example 2. Many of the XML constraint definitions are as well, but there are a couple worth highlighting. Here's how the "strikethrough" checkbox is positioned:

<constrain name="s">
  <top>
    <toAttribute reference="bold" attribute="top"/>
  </top>
  <left>
    <toAttribute reference="bold,italic,underline" attribute="right" offset="10"/>
  </left>
</constrain>

The top is aligned with the "bold" checkbox in the same way we've been doing it all along, but the left is constrained to fall ten pixels past the right of the bounding box around all three of the "bold", "italic," and "underline" checkboxes. Since "underline" is the widest, and they're all lined up along their left edges, this positions "strikethrough" just to the right of "underline." The sample text area uses the same approach, but leaves a larger gap of twenty pixels between its left edge and the second column of styles:

<constrain name="sample">
  <top>
    <toAttribute reference="caption" attribute="bottom" offset="4"/>
  </top>
  <bottom>
    <toAttribute reference="apply" attribute="top" offset="-10"/>
  </bottom>
  <left>
    <toAttribute reference="s,tt,em,strong" attribute="right" offset="20"/>
  </left>
  <right>
    <toAttribute reference="_container" attribute="right" offset="-10"/>
  </right>
</constrain>

This also nestles it below the "Sample Text:" caption and above the "Apply" button, and puts its right side ten pixels from the edge of the window.

The Java source for this example is Example3.java and the constraints are defined in example3.xml. Running it requires the same class path as Example 2.

That's all there is to using RelativeLayout. These examples provide an introduction to some of the practical ways it can be applied. I hope that reading them has brought to mind an instance or two where it would have simplified building an interface. Although there are many more useful ways to combine constraints and anchors than we've explored here, RelativeLayout can't handle every imaginable situation by itself. You'll still sometimes need to nest containers and enlist the help of other layout managers, though perhaps less often than before.

Should you enjoy experimenting beyond the examples, there are some pretty impractical arrangements you can set up, too. Nothing prevents you from constraining an attribute to a bizarrely unrelated one, even on a different axis (for example, setting the left of one component based on the height of another). Such arrangements can behave in very odd ways. Of course, unless you're trying to set up puzzles for someone along the lines of "resize this window a few times and see if you can identify the constraints I used," you're unlikely to do such a thing.

If you read on through the discussion of how RelativeLayout actually works, you'll also see that it can support completely new kinds of constraints, too. If you can think up a useful addition to AttributeConstraint and AxisConstraint, I'd love to hear about it. Another exercise that's been "left for the reader" is to add multiple-anchor support to AxisConstraint. If you do that, it would make sense to move the supporting code to an abstract skeleton that is a new ancestor to both AttributeConstraint and AxisConstraint (and any future constraint that might want to support multiple anchors). But I'm getting ahead of myself -- if you're interested in this sort of discussion, be sure to read the "under the hood" section.

Origins of RelativeLayout

Before digging in to the source code and explaining how everything works, I'd like to pause to acknowledge the books and people that were most instrumental in enabling and motivating me to create this tool.

The key concepts were first introduced (to me, anyway) by the "Custom Interdependent Layout Manager" detailed in Philip Heller and Simon Roberts' Java 2 Developer's Handbook (SYBEX). I found their FormLayout to be a terrific idea, and immediately wanted to go further with it. Their book predated the availability of convenient XML tools in Java, which forced a much more awkward and cryptic configuration mechanism on them. Although the code worked just fine in JDK 1.1, it didn't take advantage of the more modern Collections framework, nor the kind of refined object-oriented API that can be achieved in a mature Java program. In fact, with all due respect to clean and working code, many pieces seemed to have been ported just far enough from C to pass muster with the Java compiler.

I felt I owed it to the great ideas embodied by the algorithms to give them a new expression, writing a similar layout manager while thinking "What Would Joshua Do?" Joshua Bloch is, of course, the author of the truly indispensable Effective Java Programming Language Guide (Addison Wesley Professional), as well as the aforementioned Collections classes. RelativeLayout makes extensive use of such useful Java idioms as the type-safe enumeration, interfaces, and immutability. It's designed to form an extensible API so that you can come up with new constraint types of your own and plug them right in. These features will be illustrated in more depth in the next part of this series.

I hope Philip and Simon like the younger sibling inspired by their own creation. (And, actually, Philip had another impact on this project: his two-day Java University course was a big help developing my expertise in the language when I finally started using Java professionally in the spring of 2000.)

Finally, I have to thank Marc Loy again for convincing me that I should start writing for a larger audience than my project teams at work. Getting me involved in the revision of Java Swing is what pushed me over the edge into actually creating RelativeLayout, after thinking about it for almost a year.

If you'd like to look at how the internals of the layout manager actually work, my next article will examine its design and source code.

James Elliott is a senior software engineer at Singlewire Software, with fifteen years' professional experience as a systems developer.


Return to ONJava.com.

Copyright © 2009 O'Reilly Media, Inc.