|
RelativeLayout, a Constraint-Based Layout Managerby James Elliott, coauthor of Java Swing, 2nd Edition09/18/2002 |
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: |
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.
|
Related Reading Java Swing |
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.
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.
|
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:

|
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"/>
reference specifies the name of the anchor component, the
attribute of which we want to use as the basis for the constraint; in this case, _container.
This is a special name that tells RelativeLayout we're interested
in the attributes of the container in which layout is being performed. (It
is the value of the DependencyManager.ROOT_NAME constant we used
in the Java code of Example 1.)
attribute determines which of the anchor component's attributes
we want to use to get the value of our constraint. In this example, we're looking
at the top of the aboutBox frame. Capitalization
here is the same as it is for the constraint tag itself.
offset is the integer offset to be added to the reference attribute
in order to calculate the value for the attribute being constrained. This
can be omitted completely in cases where you want the offset to be zero.
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:
reference specifies the anchor component along the axes of which we're
going to pick a point for our constraint.axis determines which of the axes we want to use; it must always
be horizontal or vertical.fraction determines where along that axis we want to position
the constraint. 0.0 is the left (or top) of the component, and 1.0 is the
right (or bottom) edge, as illustrated in Figure 3.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.
|
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.
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 Berbee, with fifteen years' professional experience as a systems developer.
Return to ONJava.com.
Copyright © 2007 O'Reilly Media, Inc.