RelativeLayout, a Constraint-Based Layout Manager
Pages: 1, 2, 3, 4
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.