RelativeLayout, a Constraint-Based Layout Managerby James Elliott, coauthor of Java Swing, 2nd Edition
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.
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 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.
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
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
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
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
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:
- The top attribute of "Version" is equal to the bottom attribute of "Application Name" plus 8.
- The horizontal center attribute of "Application Name" is equal to the horizontal center of the container.
- The scrolling text field's right attribute is equal to the right of "Release Date".
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,
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