StackLayout

The more demos I write, the more effects I'd like to reuse for other demos. In some cases I'd like to reuse only a part of an effect. In the application I'm writing, I wanted to use the animated curves from Help Your Shelf. In this demo, there is a CurvePanel extending GradientPanel. My first problem was I wanted to change the gradient and I felt it was awkward to modify the base class rather than just using another implementation. I then added sprites to the demo and I wanted them to be displayed below the curves but on top of the gradient. That's why I decided to decouple every layer and use a StackLayout to produce this:

To achieve this I simply extracted each graphical element into a separate component: CurvesPanel, GradientPanel and AvatarChooser. With the StackLayout it is very easy to stack them in the right order:

JPanel pane = new JPanel();
pane.setLayout(new StackLayout());
        
GradientPanel gradient = new GradientPanel();
AvatarChooser chooser = new AvatarChooser();
CurvesPanel curves = new CurvesPanel();
        
pane.add(gradient, StackLayout.TOP);
pane.add(chooser, StackLayout.TOP);
pane.add(curves, StackLayout.TOP);

The API is very simple and let you add a new component either on top or at the bottom of the stack. Here is how the three components are stacked:

The code used to create the layout is very simple. As you can see below, it simply changes the Z order of every component and make them take as much space as possible, thus providing the same behavior than the BorderLayout.CENTER constraint. Here is the source code:

import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.LayoutManager2;
import java.awt.Rectangle;
import java.util.LinkedList;
import java.util.List;

public class StackLayout implements LayoutManager2 {
public static final String BOTTOM = "bottom";
public static final String TOP = "top";

private List components = new LinkedList();

public void addLayoutComponent(Component comp, Object constraints) {
synchronized (comp.getTreeLock()) {
if (BOTTOM.equals(constraints)) {
components.add(0, comp);
} else if (TOP.equals(constraints)) {
components.add(comp);
} else {
components.add(comp);
}
}
}

public void addLayoutComponent(String name, Component comp) {
addLayoutComponent(comp, TOP);
}

public void removeLayoutComponent(Component comp) {
synchronized (comp.getTreeLock()) {
components.remove(comp);
}
}

public float getLayoutAlignmentX(Container target) {
return 0.5f;
}

public float getLayoutAlignmentY(Container target) {
return 0.5f;
}

public void invalidateLayout(Container target) {
}

public Dimension preferredLayoutSize(Container parent) {
synchronized (parent.getTreeLock()) {
int width = 0;
int height = 0;

for (Component comp: components) {
Dimension size = comp.getPreferredSize();
width = Math.max(size.width, width);
height = Math.max(size.height, height);
}

Insets insets = parent.getInsets();
width += insets.left + insets.right;
height += insets.top + insets.bottom;

return new Dimension(width, height);
}
}

public Dimension minimumLayoutSize(Container parent) {
synchronized (parent.getTreeLock()) {
int width = 0;
int height = 0;

for (Component comp: components) {
Dimension size = comp.getMinimumSize();
width = Math.max(size.width, width);
height = Math.max(size.height, height);
}

Insets insets = parent.getInsets();
width += insets.left + insets.right;
height += insets.top + insets.bottom;

return new Dimension(width, height);
}
}

public Dimension maximumLayoutSize(Container target) {
return new Dimension(Integer.MAX_VALUE,
Integer.MAX_VALUE);
}

public void layoutContainer(Container parent) {
synchronized (parent.getTreeLock()) {
int width = parent.getWidth();
int height = parent.getHeight();

Rectangle bounds = new Rectangle(0, 0, width, height);

int componentsCount = components.size();

for (int i = 0; i
If you want to develop reusable, non opaque components, this layout can be very handy.

P.S: The white panel is actually used for debugging purpose only.

Comments are closed.