ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.


AddThis Social Bookmark Button

Inside RelativeLayout
Pages: 1, 2, 3, 4

RelativeLayout Itself

The 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 addLayoutComponent() 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 Constraint 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 addConstraint() 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, Swing calls 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 the results.

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. The way 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.

Constraint Implementations

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 manager.

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 AttributeType 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, getValue(), it loops over the anchors, tallying up the values of the desired attribute in the appropriate way, and returns the result.

Related Reading

Java and XML
Solutions to Real-World Problems
By Brett McLaughlin

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. As before, 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 Constraint interface, even for some pretty useful constraints, is quite straightforward.

XML Support

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).


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 XmlConstraintBuilder.

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 of type constrain (which has yet to be defined). Because there is no corresponding ATTLIST directive for constraint-set, this element has no attributes, only the nested elements.

Before we go on to pin down what exactly goes into a constrain 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 title component:

<constrain name="title">
      <toAttribute reference="_container" attribute="top" offset="10"/>
      <toAttribute reference="_container" attribute="horizontalCenter"/>

Ignoring for the moment the contents of the top and horizontalCenter elements, this reflects the structure mandated by the DTD: the constrain element has a name attribute with the value "title". It contains two nested elements, top and horizontalCenter, both of which are taken from the list defined by attributeName. The DTD lets the parser enforce this structure without us writing Java code to test for it.

Pages: 1, 2, 3, 4

Next Pagearrow