Thursday, July 15, 2010

Grow Your Container With This Fool Proof Method

Growing stuff is usually the subject filling my spambox... But into my inbox came a question from Terrence on how to grow a container.
Generally what he wanted was an effect similar to the one found on Google maps (that you can see in the video right here).
I tried to implement something like this in the most generic way possible which required a minor tweak to LWUIT (allowing us to restore the original preferred size of a component).

Generally the whole grow/shrink logic is contained in one method: "grow". Its relatively simple and just creates a motion between the current size of the component and the preferred size thus allowing the new component to "grow into place". The motion updates the preferred size and relayouts the form to create the smooth animation effect we see in the video.

I needed the ability to restore the default preferred size otherwise I would be stuck in the larger size after animating once. An alternative solution would have been to use a custom Container sublclass or to keep the original values stored neither one of which is my favorite...

Another "complexity" relates to the rather complex nature of preferred size, text area reports a relatively large preferred size before layout actually occurs. The reason is that it can't possibly know the target size of the container it will be placed in so it just guesses. So to support this situation I needed to layout the text area once in order to allow it to fit into place.

The code for this demo is in the incubator and pasted bellow:
public class GrowMidlet extends MIDlet {
private static final String LONG_TEXT_1 = "There is some text that should take more than one line to show so it will be cut off at some point when shown in a single line but when we show it as multiline it should work out and layout just fine and dandy... ";
private static final String LONG_TEXT_2 = "Growing text can be longer or shorter, it can be placed in the middle of the screen or anywhere really this is quite generic!";
private static final String LONG_TEXT_3 = "Using this is as simple as pie with any container type you can think of and any text length!";
public void startApp() {
try {
Display.init(this);
Resources r = Resources.open("/pimpTheme.res");
UIManager.getInstance().setThemeProps(r.getTheme(r.getThemeResourceNames()[0]));
Form f = new Form("Grow Demo");
f.addComponent(createGrowingContainer(LONG_TEXT_1, "TextAreaMe"));
f.addComponent(createGrowingContainer(LONG_TEXT_2, "TextAreaThem"));
f.addComponent(createGrowingContainer(LONG_TEXT_3, "TextAreaMe"));
f.show();
} catch (IOException ex) {
ex.printStackTrace();
}
}

private Container createGrowingContainer(String text, String uiid) {
final Container growingContainer = new Container(new BorderLayout());
final Label oneLine = new Label(text);
final Button grow = new Button("more...");
final TextArea multiLine = new TextArea(text);
final Button shrink = new Button("less...");
multiLine.setUIID("Label");
growingContainer.setUIID(uiid);
multiLine.setGrowByContent(true);
multiLine.setSingleLineTextArea(false);
growingContainer.addComponent(BorderLayout.CENTER, oneLine);
growingContainer.addComponent(BorderLayout.EAST, grow);
grow.addActionListener(new ActionListener() {

boolean firstTime = false;

public void actionPerformed(ActionEvent evt) {
growingContainer.removeAll();
growingContainer.addComponent(BorderLayout.CENTER, multiLine);
Container flow = new Container();
if (shrink.getParent() != null) {
shrink.getParent().removeAll();
}
flow.addComponent(shrink);
growingContainer.addComponent(BorderLayout.SOUTH, flow);
// the first time around the text area doesn't report the correct preferred size
// since it doesn't know its screen placement
if (firstTime) {
growingContainer.revalidate();
}
grow(growingContainer);
growingContainer.revalidate();
}
});
shrink.addActionListener(new ActionListener() {

public void actionPerformed(ActionEvent evt) {
growingContainer.removeAll();
growingContainer.addComponent(BorderLayout.CENTER, oneLine);
growingContainer.addComponent(BorderLayout.EAST, grow);
grow(growingContainer);
growingContainer.revalidate();
}
});
return growingContainer;
}

private void grow(final Component c) {
final Motion wMotion = Motion.createSplineMotion(c.getWidth(), c.getPreferredW(), 300);
final Motion hMotion = Motion.createSplineMotion(c.getHeight(), c.getPreferredH(), 300);
wMotion.start();
hMotion.start();
c.setPreferredSize(new Dimension(c.getWidth(), c.getHeight()));
c.getComponentForm().registerAnimated(new Animation() {
public boolean animate() {
if(wMotion.isFinished() && hMotion.isFinished()) {
c.getComponentForm().deregisterAnimated(this);
c.setPreferredSize(null);
c.getComponentForm().revalidate();
return false;
}
c.setPreferredSize(new Dimension(wMotion.getValue(), hMotion.getValue()));
c.getComponentForm().revalidate();
return false;
}

public void paint(Graphics g) {
}
});
}

public void pauseApp() {
}

public void destroyApp(boolean unconditional) {
}
}

4 comments:

  1. Hi..
    this blog very nice. thank you.
    but i copy this code and run then nullPointer exception on line c.setPreferredSize(null);
    I don't understand where i m wrong plz help..

    ReplyDelete
  2. hi
    thanks too
    it is very nice

    But I have the same
    exception on line c.setPreferredSize(null); :)

    do you think it my be related with the theam

    ReplyDelete
  3. You both need to use the trunk (latest sources) as I wrote in the post.

    ReplyDelete
  4. The SVN from the lwuit-incubator java.net project.

    ReplyDelete