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

advertisement

AddThis Social Bookmark Button

RelativeLayout, a Constraint-Based Layout Manager
Pages: 1, 2, 3, 4

Example 1: the Straight Java Approach.

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:


Example 1.

Pages: 1, 2, 3, 4

Next Pagearrow