Wednesday, July 30, 2008

As You Like It: Layouts any way you want (how to build a layout manager)


Layout managers in LWUIT are a remarkably powerful tool, I won't go into all the elaborate ways in which you can modify the layout in LWUIT since this is covered rather well in the tutorial and developer guide. Instead I will try to show something that is a bit under documented, mostly because its almost exactly like its Swing/AWT equivalent: building a layout manager.

A layout manager contains all the logic for positioning LWUIT components, it essentially traverses a LWUIT container and positions components absolutely based on internal logic. When we build our own component we need to take padding into consideration, when we build the layout we need to take margin into consideration. Building a layout manger involves two simple methods: layoutContainer & getPreferredSize.

layoutContainer is invoked whenever LWUIT decides the container needs rearranging, LWUIT tries to avoid calling this method and only invokes it at the last possible moment. Since this method is generally very expensive (imagine the recursion with nested layouts...), LWUIT just marks a flag indicating layout is "dirty" when something important changes and tries to avoid "reflows".

getPreferredSize allows the layout to determine the size desired for the container, this might be a difficult call to make for some layout managers that try to provide both flexibility and simplicity. Most of flow layout bugs stem from the fact that this method is just impossible to implement for flow layout. The size of the final layout won't necessarily match the requested size (it probably won't) but the requested size is taken into consideration, especially when scrolling and also when sizing parent containers.

This is a layout manager that just arranges components in a center column aligned to the middle:
/**
* Layout manager that arranges components in a center column
*
* @author Shai Almog
*/

public class CenterLayout extends Layout {
public void layoutContainer(Container parent) {
int components = parent.getComponentCount();
Style parentStyle = parent.getStyle();
int centerPos = parent.getLayoutWidth() / 2 + parentStyle.getMargin(Component.LEFT);
int y = parentStyle.getMargin(Component.TOP);
for(int iter = 0 ; iter < components ; iter++) {
Component current = parent.getComponentAt(iter);
Dimension d = current.getPreferredSize();
current.setSize(d);
current.setX(centerPos - d.getWidth() / 2);
Style currentStyle = current.getStyle();
y += currentStyle.getMargin(Component.TOP);
current.setY(y);
y += d.getHeight() + currentStyle.getMargin(Component.BOTTOM);
}
}

public Dimension getPreferredSize(Container parent) {
int components = parent.getComponentCount();
Style parentStyle = parent.getStyle();
int height = parentStyle.getMargin(Component.TOP) + parentStyle.getMargin(Component.BOTTOM);
int marginX = parentStyle.getMargin(Component.RIGHT) + parentStyle.getMargin(Component.LEFT);
int width = marginX;
for(int iter = 0 ; iter < components ; iter++) {
Component current = parent.getComponentAt(iter);
Dimension d = current.getPreferredSize();
Style currentStyle = current.getStyle();
width = Math.max(d.getWidth() + marginX + currentStyle.getMargin(Component.RIGHT) +
currentStyle.getMargin(Component.LEFT), width);
height += currentStyle.getMargin(Component.TOP) + d.getHeight() +
currentStyle.getMargin(Component.BOTTOM);
}
Dimension size = new Dimension(width, height);
return size;
}
}
Here is a simple example of using it:
Form f = new Form("Centered");
f.setLayout(new CenterLayout());
for(int iter = 1 ; iter < 20 ; iter++) {
f.addComponent(new Button("Button: " + iter));
}
f.addComponent(new Button("Really Wide Button Text!!!"));
f.show();

6 comments:

  1. You probably meant getPadding in a few places in there instead of getMargin, right? For the parent, isn't the internal spacing 'padding' and for the child, the external spacing 'margin'?

    ReplyDelete
  2. Margin is correct. The layout places the margin while it is the responsibility of the component to place the padding. Margin is the external spacing hence it is placed externally in the layout manager.

    ReplyDelete
  3. Hey how can i align components in the middle of the dialog vertically.

    This class aligns them centrally but the components are still at the top of the dialog. I need them in the center

    ReplyDelete
  4. Hi, can you edit the layout so it can also align the components vertically at the center? I've tried to do it, but everything gets off-screen, i think..

    ReplyDelete
  5. @Erdi: you are probably right, I hacked this in a few minutes ;-)

    To align vertically make sure to request a higher preferred size value. Also make sure to calculate whether the first component should appear and add the appropriate value to the y axis of said component.

    ReplyDelete