Custom Layout Manager: PyramidLayout
宸叉湁澶鍏充簬鑷畾涔夐儴灞綆$悊鍣ㄧ殑鏂囩珷浜嗐傛湰鏂囦粎鏄竴綃囧涔?fàn)绗旇畮图屾弿杩颁簡濡備綍瀹炵庮C竴縐嶅儚鍫嗛噾瀛楀浼肩殑閮ㄥ眬綆$悊鍣紝寰堢畝鍗曪紝涔熸湁鐐瑰効鎰忔濓紝鍙兘浣犱篃浼?xì)鎰熷叴瓒g殑銆?2012.07.17鏈鍚庢洿鏂?
I have developed Swing application for several years, although I'm not professional GUI developer, I'm shamed of never creating any custom layout manager. Maybe the existing Swing layout managers are too powerful to create new ones. At least, GridBagLayout is powerful enough for my real works. And we have much more flexible GroupLayout and SpringLayout, of course, both of them are too complex, in fact I never use them directly. However I indirectly take advantage of GroupLayout due to using NetBeans' GUI designer Matisse.
1. Layout Manager basics
Let's start with some layout manager foundation. Before this time I learn to customize layout, I always think layout manager is very mysterious. Layout is like a magic player that put a variety of components to right positions in containers. I haven't browsed any code of any layout, event the simplest one. That's why I think layout is mystery. But it's simple for me now.
Generally, all of layout implements one or both of LayoutManager and LayoutManager2 interfaces. LayoutManager2 is LayoutManager's sub-interface, then if someone implements LayoutManager2 that means it really implements LayoutManager. Mostly all modern layouts implements LayoutManager2.
Interface LayoutManager defines the basic methods must be implemented by every layout, all of them are intuitional: add new component--addLayoutComponent(); remove component--removeLayoutComponent(); calculate preferred size--preferredLayoutSize(); calculate minimum size--minimumLayoutSize(); how to layout the components--layoutContainer(). Absolutely, the layoutContainer() method is essential, you must instruct the parent container how to allocate space(bounds) for every component.
The extension interface LayoutManager2 introduces more methods that if you have to: support constraints--addLayoutComponent(Component, Object); specify maximum size--maximumLayoutSize(); specify alignment--getLayoutAlignmentX() and getLayoutAlignmentY(); destroy specific caches or reset some variables when invaliding container--invalidateLayout().
2. PyramidLayout
Now let's feature a simple and funny layout manager--PyramidLayout. The layout allows container to add components like building a Pyramid, as shown as the image below,
As the above, PyramidLayout puts the first component on the bottom, then puts the second on top of the first, but its bounds is smaller, ... It looks like a Pyramid, doesn't it? Here is the full codes,
public class PyramidLayout implements LayoutManager2 {
private List<Component> comps = new LinkedList<Component>();
public void addLayoutComponent(final Component comp,
final Object constraints) {
synchronized (comp.getTreeLock()) {
comps.add(comp);
}
}
public void addLayoutComponent(final String name, final Component comp) {
addLayoutComponent(comp, null);
}
public void removeLayoutComponent(final Component comp) {
synchronized (comp.getTreeLock()) {
comps.remove(comp);
}
}
public float getLayoutAlignmentX(final Container target) {
return 0.5F;
}
public float getLayoutAlignmentY(final Container target) {
return 0.5F;
}
public void invalidateLayout(final Container target) {
System.out.println();
}
public Dimension preferredLayoutSize(final Container parent) {
synchronized (parent.getTreeLock()) {
Insets insets = parent.getInsets();
int width = insets.left + insets.right;
int height = insets.top + insets.bottom;
if (comps.size() == 0) {
return new Dimension(width, height);
}
Dimension size = comps.get(0).getPreferredSize();
width += size.width;
height += size.height;
return new Dimension(width, height);
}
}
public Dimension minimumLayoutSize(final Container parent) {
synchronized (parent.getTreeLock()) {
Insets insets = parent.getInsets();
int width = insets.left + insets.right;
int height = insets.top + insets.bottom;
if (comps.size() == 0) {
return new Dimension(width, height);
}
Dimension size = comps.get(0).getMinimumSize();
width += size.width;
height += size.height;
return new Dimension(width, height);
}
}
public Dimension maximumLayoutSize(final Container target) {
return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
}
public void layoutContainer(final Container parent) {
synchronized (parent.getTreeLock()) {
Dimension parentSize = parent.getSize();
int compsCount = comps.size();
Dimension step = new Dimension(parentSize.width / (2 * compsCount),
parentSize.height / (2 * compsCount));
for (int i = 0; i < compsCount; i++) {
Component comp = comps.get(i);
comp.setBounds(calcBounds(parentSize, step, i));
parent.setComponentZOrder(comp, compsCount - i - 1);
}
}
}
private Rectangle calcBounds(Dimension parentSize, Dimension step, int index) {
int x = step.width * index;
int y = step.height * index;
int width = parentSize.width - step.width * 2 * index;
int height = parentSize.height - step.height * 2 * index;
return new Rectangle(x, y, width, height);
}
}
Collection instance "comps" manages all of components, in this case, I take a LinkedList object to add and remove UI components. The layout doesn't concern any constraint, so the two addLayoutComponent() methods have the same actions. Please see the codes for details.
As aforementioned, layoutContainer() method really takes charge of layouting the components. The key work is allocating space for each component, namely, specifying the bounds. Calculating bounds values just applies the simplest arithmetic operations.
According to the intention, the bottom component fills the whole parent container, so it determines the preferred and the minimum sizes. For details, please take a look at methods preferredLayoutSize() and minimumLayoutSize(). Since the layout manager doesn't take care of the maximum size, the maximumLayoutSize() method simply returns a constant value.