Three manipulations are defined for springs. Recall that the calculated values are not actually hard-coded into the resulting springs. References to
s2 are stored, so that if either spring changes, the resulting springs also change. (This is done with the help of a private inner class extension of
Spring known as a "proxy spring.")
This method returns a new spring the properties of which represent the
Math.max() of the respective properties of
s2. In other words, the minimum property of the new spring is the max of
public static Spring max(Spring s1, Spring s2)
This method returns a new spring, the properties of which represent the negative of the respective properties of
s1. In other words, the minimum property of the new spring is
public static Spring minus(Spring s)
This method returns a new spring, the properties of which represent the sum of the respective properties of
s2. In other words, the minimum property of the new spring is equal to
s1.getMinimum() + s2.getMinimum().
public static Spring sum(Spring s1, Spring s2)
As mentioned earlier, you can combine the manipulation methods to create other operations. In particular,
Spring.sum(s1, Spring.minus(s2)) returns the difference of the respective properties in
Math.min() function can be mimicked using
Spring.minus(Spring.max(Spring.minus(s1), Spring.minus(s2))). Try it on paper -- it really works!
The combination of
Spring math operations and constraints can make certain layouts easy to create (and easy to manipulate). That's "easy" in the "if you're designing a GUI builder" sense, of course. Figure 4 shows four buttons laid out in a vertical row with various
Spring constraints holding them in place.
Figure 4. Vertically stacked buttons in a SpringLayout.
To show off more of the
Spring constraint combinations, we varied the layout code for these buttons:
// We'll leave all buttons at their preferred widths and heights // b1 gets placed at (10,10) c.add(b1); sl.getConstraints(b1).setX(offsetS); sl.getConstraints(b1).setY(offsetS); // b2 gets placed at (10, offset + b1.height + offset) c.add(b2); sl.getConstraints(b2).setX(offsetS); sl.getConstraints(b2).setY(Spring.sum(Spring.sum(offsetS, sl.getConstraints(b1).getHeight()), offsetS)); // b3 gets placed at (10, b2.south + offset) c.add(b3); sl.getConstraints(b3).setX(offsetS); sl.getConstraints(b3).setY(Spring.sum(offsetS, sl.getConstraint(SpringLayout.SOUTH, b2))); // b4 gets placed at (b3.west, b3.south + offset) c.add(b4); sl.putConstraint(SpringLayout.WEST, b4, 0, SpringLayout.WEST, b3); sl.putConstraint(SpringLayout.NORTH, b4, offsetS, SpringLayout.SOUTH, b3);
You could use any one of the techniques shown on all of the buttons, if you were so inclined. That said, there are a few consequences to the constraints we created in this example. For example, look at the
y constraint of
b2. It is simply the sum of two offsets and the height of
b1. It is not dependent on the bottom edge of
b1. It doesn't care where
b1 is placed. The
y constraints of
b4, however, are dependent. If
b2 moves down, so does
b3 -- and if
b3 moves down, so does
One other fun constraint in this example is the left edge of
b4. We tied it to the left edge of
b3. If you change the
x constraint of
There are some things that cannot be duplicated using
max(). For those things, you can simply extend the
Spring class. The compass navigation example in Figure 2 keeps the North and South buttons horizontally centered. (Of course, the East and West buttons are vertically centered.) To keep the buttons centered, even after the user resizes the frame, we need a new spring that returns the center of a parent spring. That can be built as we do in the
If you look a bit closer, this class can actually handle any multiplier value. The factory method
half() produces the spring we need most often, but you can use the public constructor to supply an alternative. You could certainly write other factory methods for common values you find useful. Maybe a
goldenMean() method is in your future?
One method you want to pay attention to is
setValue(). In several derived springs (like our
setValue() call does not make sense. Normally, we throw an
UnsupportedOperationException to indicate that this required method from our abstract parent does not really apply; however, the special value
UNSET can be used to help in a particular scenario: value caching.
If the current value of the spring comes from an expensive operation, you can cache that value. If your spring is bound to another spring, such as the border of your container, changing the size of the container causes a chain of
UNSET values to be passed to dependent springs. You could watch for
UNSET and recalculate your spring values only when you receive it. (Try uncommenting the
println() in our
setValue() method and rerun the example. You should see four
UNSET calls each time you resize the frame -- one for each button.)
While it may take a few tries to get your brain around
SpringLayout and spring math, you can accomplish some complex layouts with one container and a single, efficient layout manager.
Marc Loy is a trainer and media specialist in Cincinnati, OH. When he's not working with digital video and DVDs, he's programming in Java.
Return to ONJava.com.