RelativeLayout, a Constraint-Based Layout Manager
Pages: 1, 2, 3, 4
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"/>
referencespecifies 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 tellsRelativeLayoutwe're interested in the attributes of the container in which layout is being performed. (It is the value of theDependencyManager.ROOT_NAMEconstant we used in the Java code of Example 1.)attributedetermines 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 thetopof theaboutBoxframe. Capitalization here is the same as it is for the constraint tag itself.offsetis 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:
referencespecifies the anchor component along the axes of which we're going to pick a point for our constraint.axisdetermines which of the axes we want to use; it must always behorizontalorvertical.fractiondetermines 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.