RelativeLayout class pulls everything together,
and is the main point of contact for your own programs as well as for Swing.
It implements the
LayoutManager2 interface so you can assign it
as the layout manager of a Swing container. Its
methods create the
ComponentSpecifications objects to help with
the dependency-managed layout process, and keep track of all components being
managed. If you look at the version that accepts a constraints object, you'll
see I was thinking of building a way to let you pass in a full set of
definitions at the time you added an object to the container, but it turned
out that the XML-based approach was more convenient than this idea anyway, so
it was never implemented. Instead, you either use the
method to manually register your constraints, or, more likely, let
XmlConstraintBuilder do it for you.
When it comes time for the container to be laid out according to your constraints,
layoutContainer(). This figures out the available size
of the container, calls the
resolveComponents() method to perform
all of the bookkeeping and calculation described in the previous section (although
most of it can be skipped if no constraints have been changed since the last
time layout was performed), then positions and sizes the components based on
There are a couple of other methods used to estimate the preferred and minimum
sizes for the layout (which are based on the constraints and the preferred or
minimum sizes of all of the components). Swing uses these when it's trying to figure
out how much space to give the container if it's nested in another container
that cares, and (in some Look-and-Feels) to prevent you from shrinking a window
so much that it crushes components into smaller spaces than they want to fit.
RelativeLayout estimates these sizes is to pretend the
window is as small as possible (zero by zero), perform layout based on the minimum
(or preferred) sizes of the components, then measure how badly they didn't fit.
We've already looked at the
Constraint interface, which sets up
the general behavior that is required of a constraint within the layout process,
but the actual details are dependent on classes that implement the interface.
RelativeLayout ships with two such implementations, and by creating new ones
it's possible to add new capabilities to the layout
AttributeConstraint gets its value from an attribute
of an "anchor" component, plus some fixed offset (which may be zero).
It also can work with a list of several anchor components, in which case the
attribute is calculated from the smallest bounding box that encloses
the components. The implementation is pretty straightforward: it keeps track
of the component(s) on which it bases its value, as well as the
to use and the offset. The constructors set these all up, and build a cached
list of dependencies on the specified anchor components to speed things up later
on. Supporting the mandatory
getDependencies() method is therefore
trivial -- it just returns this list. For the other required method,
it loops over the anchors, tallying up the values of the desired attribute in
the appropriate way, and returns the result.
AxisConstraint lets you specify a fractional
point along an axis of an anchor component as the source of its value. The constructor
records all of the necessary information: the component of interest, the axis you're
using, and the position along that axis. It also creates a cached list of its
(two) dependencies. If you're using a horizontal axis, it needs to know the left
edge and width of the anchor; for a vertical axis, it uses the top and height.
getDependencies() can simply return this list, while
getValue() looks up these two attributes and does the simple math
required to return its result.
As you can see, the code required to implement the
interface, even for some pretty useful constraints, is quite straightforward.
All of the above classes are enough to implement the dependency-managed relative
layout manager. In fact, they were written first, and
RelativeLayout was useful
with just them. I wanted to be able to use a more compact way to express dependencies
and constraints, though, and XML configuration files were an appealing solution.
To take best advantage of a validating XML parser (so the Java code need not worry about whether the XML file has the right elements and structure), the first step is to come up with a specification for the format of the configuration file; one that can be understood by the parser. This takes the form of an XML Document Type Definition (DTD).
The XML DTD
Creating a DTD is something of an arcane art (or at least, I still find it so). I'd previously created a couple of simple ones, but I definitely learned a lot creating this one. I wouldn't have been able to tackle it without help from both Brett McLaughlin's Java & XML (2nd Edition, O'Reilly & Associates) and Bob DuCharme's XML: The Annotated Specification (Prentice Hall). Although the latter is a little dry, it's the only place where I've found detailed explanations and examples of all of the pieces that can go into a DTD, and how they constrain the parser.
The DTD is short enough to walk through the entire document here, in case it
helps anyone else have some of the "a-ha" experiences I did when assembling
it. If this doesn't interest you, or becomes hard going as we dig into the weeds,
feel free to jump ahead to the discussion of
The beginning of the file is just a comment:
<!-- constraint-set.dtd, created Monday, May 20, 2002. Defines the syntax of a constraint-set specification XML document. $Id: constraint-set.dtd,v 1.3 2002/08/16 05:13:04 jim Exp $ -->
The stuff on the last line enclosed between
$ signs is just a CVS
ID, information automatically provided by the source-control system I use, identifying
the version of the file and the time it was last committed. You'll see them
in all of the source files. The real meat of the file begins on the next line:
<!ELEMENT constraint-set (constrain*)>
This states that the constraint-set document contains zero or more elements
constrain (which has yet to be defined). Because there
is no corresponding
ATTLIST directive for
this element has no attributes, only the nested elements.
Before we go on to pin down what exactly goes into a
element, we pause to declare an entity that will make the file more readable.
You can think of an entity as a macro; wherever we use it later in the file,
it gets expanded into the value defined for it. Our entity lists the valid attribute
names you can use with a constraint:
<!ENTITY % attributeName "(left | horizontalCenter | right | width | top | verticalCenter | bottom | height)">
Each of these attribute names is an element that will also be defined later. (Remember that there are two different meanings of "attribute" that are relevant here: positional attributes of components used in laying them out, and the XML syntax notion of an attribute associated with an element in the document. This is talking about the layout kind.) First, here's where we use them:
<!ELEMENT constrain ((%attributeName;)+)>
<!ATTLIST constrain name CDATA #REQUIRED >
This is the promised definition of the
constrain element. It states
that this element contains at least one and possibly many nested elements, which
are chosen from the set defined by the
attributeName entity. It
also has a mandatory
name attribute (of the XML sort). This is
getting a little dangerously abstract; let's look at an example of what this
means, to bring it back to Earth (even though we've not yet defined the details
of any of the
attributeName elements themselves yet). From the
XML used in Example 2, here's the
constraint for the
<constrain name="title"> <top> <toAttribute reference="_container" attribute="top" offset="10"/> </top> <horizontalCenter> <toAttribute reference="_container" attribute="horizontalCenter"/> </horizontalCenter> </constrain>
Ignoring for the moment the contents of the
elements, this reflects the structure mandated by the DTD: the
element has a
name attribute with the value
It contains two nested elements,
both of which are taken from the list defined by
The DTD lets the parser enforce this structure without us writing Java code
to test for it.